package spreadsheet;

import java.util.LinkedList;

/**
 * This class is a back-end for a spreadsheet program. It handles the accepting
 * of formulas, the getting of a cell value, and it determines the order in
 * which the cells need to be queried.
 * 
 * @author Jonathan Fangmeyer
 * @version 2010-03-03
 */
public class Spreadsheet {

	/**
	 * The default number of rows.
	 */
	public static final int DEFAULT_ROW_SIZE = 10;

	/**
	 * The default number of columns.
	 */
	public static final int DEFAULT_COLUMN_SIZE = 10;

	/**
	 * The 2D array of cells that hold the data of the spreadsheet.
	 */
	private final Cell[][] cells;

	/**
	 * A no argument constructor that creates the table in the default size.
	 */
	public Spreadsheet() {
		this(DEFAULT_ROW_SIZE, DEFAULT_COLUMN_SIZE);
	}

	/**
	 * A constructor that accepts a column and row for the size of the table.
	 * 
	 * @param the_row_size the number of rows for the spreadsheet.
	 * @param the_column_size the number of columns for the spreadsheet.
	 */
	public Spreadsheet(final int the_row_size, final int the_column_size) {
		cells = new Cell[the_row_size][the_column_size];
	}

	/**
	 * Returns a 2D array of the values of each cell in the form of a String.
	 * 
	 * @return 2D array of the values.
	 */
	public String[][] getCells() {
		String[][] copy_of_data = new String[cells.length][cells[0].length];
		for (int i = 0; i < cells.length; i++) {
			for (int j = 0; j < cells[0].length; j++) {
				if (cells[i][j] != null) {
					copy_of_data[i][j] = Integer.valueOf(
							(cells[i][j].getValue())).toString();
				}
			}
		}
		return copy_of_data;
	}

	/**
	 * Gets a specific cell's formula aka user's input.
	 * 
	 * @return String representation of the users input.
	 */
	public String getCellFormula(final int the_row, final int the_column) {
		if (cells[the_row][the_column] != null) {
			return cells[the_row][the_column].getFormula();
		}
		return null;
	}

	/**
	 * Gets the value of a cell based on two integers and returns an integer.
	 * 
	 * @return the value of the cell that is queried.
	 */
	public int getCellValueInt(final int the_row, final int the_column) {
		if (cells[the_row][the_column] == null) {
			return 0;
		}
		return cells[the_row][the_column].getValue();
	}


	/**
	 * Gets the value of a cell based on two integers and returns an integer.
	 * 
	 * @param the_row the row of the cell that is being queried.
	 * @param the_column column of the cell that is being queried.
	 * @return
	 */
	public String getCellValueString(final int the_row, final int the_column) {
		if (cells[the_row][the_column] == null) {
			return null;
		}
		return Integer.valueOf(cells[the_row][the_column].getValue())
				.toString();
	}

	/**
	 * Gets the number of columns of the spreadsheet.
	 * 
	 * @return the number of columns.
	 */
	public int getColumnSize() {
		return cells[0].length;
	}

	/**
	 * Gets the number of rows of the spreadsheet.
	 * 
	 * @return the number of rows.
	 */
	public int getRowSize() {
		return cells.length;
	}
	
	/**
	 * Sets a specific cell's formula aka the user's new input.
	 * 
	 * @param the_row the row of the cell that getting the input.
	 * @param the_column column of the cell that getting the input.
	 * @param the_formula the new formula of the the cell.
	 * @throws Exception Throws a "Cycle Error" if there is a cycle in the spreadsheet.
	 */
	public void setCellString(int the_row, int the_column, String the_formula)
			throws Exception {
		if (cells[the_row][the_column] == null) {
			cells[the_row][the_column] = new Cell(the_row, the_column, this);
		}
		cells[the_row][the_column].setFormula(the_formula);
		cells[the_row][the_column].constructCell();
		evaluateCells();
	}

	/**
	 * Gets the actual cell from two integers.
	 * 
	 * @param the_row the row of the cell that is being accessed.
	 * @param the_column column of the cell that is being accessed.
	 * @return the desired cell.
	 */
	protected Cell getCell(final int the_row, final int the_column) {
		return cells[the_row][the_column];
	}

	/**
	 * Evaluates the cells in a topological order using the indegrees of each cell.
	 * 
	 * @throws Exception Throws a "Cycle Error" if there is a cycle in the spreadsheet.
	 */
	private void evaluateCells() throws Exception {
		LinkedList<Cell> indegrees_of_0 = new LinkedList<Cell>();
		int good_cell_counter = 0;
		int degree_of_0_counter = 0;
		for (int row = 0; row < cells.length; row++) {
			for (int column = 0; column < cells[row].length; column++) {
				Cell cell = cells[row][column];
				if (cell != null) {
					good_cell_counter++;
					cell.resetIndegree();
					if (cell.getIndegree() == 0)
						indegrees_of_0.push(cell);
				}
			}
		}
		while (!indegrees_of_0.isEmpty()) {
			Cell cell_of_0 = indegrees_of_0.pop();
			cell_of_0.calculateValue();
			degree_of_0_counter++;
			for (Cell provider_of_cell : cell_of_0.getDependents()) {
				if (provider_of_cell != null) {
					provider_of_cell.decreaseIndegree();
					if (provider_of_cell.getIndegree() == 0) {
						indegrees_of_0.push(provider_of_cell);
					}
				}

			}
		}
		if (degree_of_0_counter != good_cell_counter) {
			throw new Exception("Cycle Error");
		}
	}
}
